package com.gateway.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.common.domain.pojo.Result;
import com.common.handler.RedisKeysHandler;
import com.common.properties.SecurityProperties;
import com.common.tool.JwtTool;
import jakarta.annotation.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

public class SecurityTokenAuthenticationWebFilter implements WebFilter {

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        List<String> list = request.getHeaders().get(securityProperties.getAuthentication().getAccess_name());
        if (list != null && !list.isEmpty()) {
            String token = list.get(0);
            // 得到token加密的secretKey
            String accessSecretKey = securityProperties.getAuthentication().getAccess_secretKey();
            // 解析token得到 用户名
            String username;
            try {
                username = JwtTool.getUsername(token, accessSecretKey);
            } catch (Exception e) {
                // 直接结束本次请求
                return authenticationFail(response);
            }
            // 校验登入的令牌是否与redis存储的登入令牌一致
            String targetToken = stringRedisTemplate.opsForValue().get(RedisKeysHandler.getTokenKey(JwtTool.getId(token, accessSecretKey)));
            if (!Objects.equals(targetToken, token)) {
                return authenticationFail(response);
            }
            // 登入令牌校验
            if (StrUtil.isEmptyIfStr(username) || !JwtTool.validateToken(token, accessSecretKey)){
                return authenticationFail(response);
            }
            return ReactiveSecurityContextHolder.getContext()
                    .switchIfEmpty(Mono.defer(() -> {
                        // 当没有安全上下文时，创建一个新的
                        SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
                        // 创建认证信息
                        UsernamePasswordAuthenticationToken authToken =
                                new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.NO_AUTHORITIES);
                        emptyContext.setAuthentication(authToken);
                        return Mono.just(emptyContext);
                    }))
                    .flatMap(securityContext -> {
                        // 使用 ReactiveSecurityContextHolder.withSecurityContext() 正确传递上下文
                        Context context = ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext));
                        // 将Context应用到后续的过滤器链中
                        return chain.filter(exchange).contextWrite(context);
                    });
        }
        return chain.filter(exchange);
    }

    private static Mono<Void> authenticationFail(ServerHttpResponse response) {
        response.setStatusCode(HttpStatusCode.valueOf(HttpStatus.HTTP_UNAUTHORIZED)); // 设置HTTP状态码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        DataBuffer buffer = response.bufferFactory().wrap(JSONUtil.toJsonStr(Result.error(401, "身份校验失败 请重新登入")).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

}
